import 'dart:typed_data';

import 'hex_utils.dart';

extension CUByteArrayStringExtensions on String {
  String reversalEvery2Charts({bool hasSpace = false}) {
    final hex = this.addSpaceEvery2Charts();
    return hex.split(" ").reversed.join(hasSpace ? " " : "");
  }

  String addSpaceEvery2Charts() {
    final hex = this.replaceAll(" ", "");
    final sb = StringBuffer();
    for (var i = 0; i < hex.length / 2; i++) {
      sb.write(hex.substring(i * 2, i * 2 + 2));
      sb.write(" ");
    }
    return sb.toString().trim();
  }
}

extension CUByteArrayByteArrayExtensions on List<int> {
  String toHexString({bool hasSpace = true}) {
    return this.map((e) => e.toRadixString(16).padLeft(2, '0').toUpperCase()).join(hasSpace ? " " : "");
  }

  String toAsciiString({bool hasSpace = false}) {
    return this.map((e) => String.fromCharCode(e)).join(hasSpace ? " " : "");
  }

  int? readByte8({int offset = 0}) {
    if (throwOffsetError(this, offset, 1)) return null;
    return this[offset];
  }

  int? readUByte8(int offset) {
    if (throwOffsetError(this, offset, 1)) return null;
    return this[offset] & 0xFF;
  }

  int? readByte4(int offset) {
    if (throwOffsetError(this, offset, 1)) return null;
    return this[offset] & 0x0F;
  }

  int? readUByte4({int offset = 0}) {
    if (throwOffsetError(this, offset, 1)) return null;
    return (this[offset] & 0xFF) & 0x0F;
  }

  int? readByteRange(int offset, int start, int end) {
    if (throwOffsetError(this, offset, 1)) return null;
    return (this[offset] >> start) & ((1 << (end - start + 1)) - 1);
  }

  int? readInt8(int offset) {
    return readByte8(offset: offset);
  }

  int? readInt4(int offset) {
    return readByte4(offset);
  }

  int? readUInt8(int offset) {
    if (throwOffsetError(this, offset, 1)) return null;
    return this[offset] & 0xFF;
  }

  int? readInt16BE(int offset) {
    if (throwOffsetError(this, offset, 2)) return null;
    return (this[offset] << 8) | (this[offset + 1] & 0xFF);
  }

  int? readUInt16BE(int offset) {
    if (throwOffsetError(this, offset, 2)) return null;
    return ((this[offset] & 0xFF) << 8) | (this[offset + 1] & 0xFF);
  }

  int? readInt16LE(int offset) {
    if (throwOffsetError(this, offset, 2)) return null;
    return (this[offset + 1] << 8) | (this[offset] & 0xFF);
  }

  int? readUInt16LE(int offset) {
    if (throwOffsetError(this, offset, 2)) return null;
    return ((this[offset + 1] & 0xFF) << 8) | (this[offset] & 0xFF);
  }

  int? readInt32BE(int offset) {
    if (throwOffsetError(this, offset, 4)) return null;
    return (this[offset] << 24) | ((this[offset + 1] & 0xFF) << 16) | ((this[offset + 2] & 0xFF) << 8) | (this[offset + 3] & 0xFF);
  }

  int? readUInt32BE(int offset) {
    if (throwOffsetError(this, offset, 4)) return null;
    return ((this[offset] & 0xFF) << 24) | ((this[offset + 1] & 0xFF) << 16) | ((this[offset + 2] & 0xFF) << 8) | (this[offset + 3] & 0xFF);
  }

  int? readInt32LE(int offset) {
    if (throwOffsetError(this, offset, 4)) return null;
    return (this[offset + 3] << 24) | ((this[offset + 2] & 0xFF) << 16) | ((this[offset + 1] & 0xFF) << 8) | (this[offset] & 0xFF);
  }

  int? readInt64LE(int offset) {
    if (throwOffsetError(this, offset, 8)) return null;
    return (this[offset + 7] << 56) |
        ((this[offset + 6] & 0xFF) << 48) |
        ((this[offset + 5] & 0xFF) << 40) |
        ((this[offset + 4] & 0xFF) << 32) |
        ((this[offset + 3] & 0xFF) << 24) |
        ((this[offset + 2] & 0xFF) << 16) |
        ((this[offset + 1] & 0xFF) << 8) |
        (this[offset] & 0xFF);
  }

  int? readUInt32LE(int offset) {
    if (throwOffsetError(this, offset, 4)) return null;
    return ((this[offset + 3] & 0xFF) << 24) | ((this[offset + 2] & 0xFF) << 16) | ((this[offset + 1] & 0xFF) << 8) | (this[offset] & 0xFF);
  }

  int? readUInt64LE(int offset) {
    if (throwOffsetError(this, offset, 8)) return null;
    return ((this[offset + 7] & 0xFF) << 56) |
        ((this[offset + 6] & 0xFF) << 48) |
        ((this[offset + 5] & 0xFF) << 40) |
        ((this[offset + 4] & 0xFF) << 32) |
        ((this[offset + 3] & 0xFF) << 24) |
        ((this[offset + 2] & 0xFF) << 16) |
        ((this[offset + 1] & 0xFF) << 8) |
        (this[offset] & 0xFF);
  }

  double? readFloatBE(int offset) {
    final int32 = readInt32BE(offset);
    return int32 != null ? intBitsToFloat(int32) : null;
  }

  double? readFloatLE(int offset) {
    final int32 = readInt32LE(offset);
    return int32 != null ? intBitsToFloat(int32) : null;
  }

  double intBitsToFloat(int bits) {
    final bytes = ByteData(4);
    bytes.setInt32(0, bits, Endian.big);
    return bytes.getFloat32(0, Endian.big);
  }

  int floatToIntBits(double bits) {
    final bytes = ByteData(8);
    bytes.setFloat64(0, bits, Endian.big);
    return bytes.getInt32(0, Endian.big);
  }

  Uint8List readByteArrayBE(int offset, int byteLength) {
    if (throwOffsetError(this, offset)) return Uint8List(0);
    return readListBE(offset, byteLength).toByteArray();
  }

  List<int> readListBE(int offset, int byteLength) {
    if (throwOffsetError(this, offset)) return [];
    return sublist(offset, (offset + byteLength) > length ? length : offset + byteLength);
  }

  Uint8List readByteArrayLE(int offset, int byteLength) {
    if (throwOffsetError(this, offset)) return Uint8List(0);
    return readListLE(offset, byteLength).toByteArray();
  }

  List<int> readListLE(int offset, int byteLength) {
    if (throwOffsetError(this, offset)) return [];
    return readListBE(offset, byteLength).reversed.toList();
  }

  String readStringBE({int offset = 0, int byteLength = 1, String encoding = "hex", bool hasSpace = false}) {
    final bytes = readByteArrayBE(offset, byteLength);
    switch (encoding.toLowerCase()) {
      case "hex":
        return bytes.toHexString(hasSpace: hasSpace);
      case "ascii":
        return bytes.toAsciiString(hasSpace: hasSpace);
      default:
        return "";
    }
  }

  String readStringLE({int offset = 0, int byteLength = 1, String encoding = "hex", bool hasSpace = false}) {
    final bytes = readByteArrayLE(offset, byteLength);
    switch (encoding.toLowerCase()) {
      case "hex":
        return bytes.toHexString(hasSpace: hasSpace);
      case "ascii":
        return bytes.toAsciiString(hasSpace: hasSpace);
      default:
        return "";
    }
  }

  bool writeBit(bool value, {int offset = 0, int bitOffset = 0}) {
    if (throwOffsetError(this, offset)) return false;
    this[offset] = (this[offset] & ~(1 << bitOffset)) | (value ? (1 << bitOffset) : 0);
    return true;
  }

  bool writeByte4(int value, {int offset = 0}) {
    if (throwOffsetError(this, offset)) return false;
    this[offset] = (this[offset] & 0xF0) | (value & 0x0F);
    return true;
  }

  bool writeByteHigh4(int value, {int offset = 0}) {
    if (throwOffsetError(this, offset)) return false;
    this[offset] = (this[offset] & 0x0F) | ((value & 0x0F) << 4);
    return true;
  }

  bool writeInt8(int value, {int offset = 0}) {
    if (throwOffsetError(this, offset)) return false;
    this[offset] = value.toUnsigned(8);
    return true;
  }

  bool writeInt16BE(int value, {int offset = 0}) {
    if (throwOffsetError(this, offset, 2)) return false;
    this[offset] = (value >> 8).toUnsigned(8);
    this[offset + 1] = value.toUnsigned(8);
    return true;
  }

  bool writeInt16LE(int value, {int offset = 0}) {
    if (throwOffsetError(this, offset, 2)) return false;
    this[offset] = value.toUnsigned(8);
    this[offset + 1] = (value >> 8).toUnsigned(8);
    return true;
  }

  bool writeInt32BE(int value, {int offset = 0}) {
    if (throwOffsetError(this, offset, 4)) return false;
    this[offset] = (value >> 24).toUnsigned(8);
    this[offset + 1] = (value >> 16).toUnsigned(8);
    this[offset + 2] = (value >> 8).toUnsigned(8);
    this[offset + 3] = value.toUnsigned(8);
    return true;
  }

  bool writeInt32LE(int value, {int offset = 0}) {
    if (throwOffsetError(this, offset, 4)) return false;
    this[offset] = value.toUnsigned(8);
    this[offset + 1] = (value >> 8).toUnsigned(8);
    this[offset + 2] = (value >> 16).toUnsigned(8);
    this[offset + 3] = (value >> 24).toUnsigned(8);
    return true;
  }

  bool writeFloatBE(double value, {int offset = 0}) {
    if (throwOffsetError(this, offset, 4)) return false;
    final int32 = floatToIntBits(value);
    return writeInt32BE(int32, offset: offset);
  }

  bool writeFloatLE(double value, {int offset = 0}) {
    if (throwOffsetError(this, offset, 4)) return false;
    final int32 = floatToIntBits(value);
    return writeInt32LE(int32, offset: offset);
  }

  // 指定位置写入ByteArray
  bool writeByteArrayBE(
    Uint8List byteArray, {
    int offset = 0,
    int length = 0,
  }) {
    length = length == 0 ? byteArray.length : length;
    return writeStringBE2(byteArray.toHexString(), offset: offset, length: length);
  }

  bool writeByteArrayLE(
    Uint8List byteArray, {
    int offset = 0,
    int length = 0,
  }) {
    length = length == 0 ? byteArray.length : length;
    return writeStringLE2(byteArray.toHexString(), offset: offset, length: length);
  }

  Uint8List insertByteArrayBE(Uint8List insertArray, {int originalIndex = 0, int insertArrayOffset = 0, int insertArrayLength = 0}) {
    final byteArrayPre = sublist(0, originalIndex);
    final byteArrayLast = sublist(originalIndex, length);
    final insertFinalArray = insertArray.sublist(insertArrayOffset, insertArrayOffset + insertArrayLength);
    return Uint8List.fromList([...byteArrayPre, ...insertFinalArray, ...byteArrayLast]);
  }

  Uint8List insertByteArrayLE(Uint8List insertArray, {int originalIndex = 0, int insertArrayOffset = 0, int insertArrayLength = 0}) {
    insertArray = insertArray.reversed.toByteArray();
    final byteArrayPre = sublist(0, originalIndex);
    final byteArrayLast = sublist(originalIndex, length);
    final insertFinalArray = insertArray.sublist(insertArrayOffset, insertArrayOffset + insertArrayLength);
    return Uint8List.fromList([...byteArrayPre, ...insertFinalArray, ...byteArrayLast]);
  }

  bool writeStringBE(String str, {int offset = 0, String encoding = "hex"}) {
    if (throwOffsetError(this, offset)) return false;
    switch (encoding.toLowerCase()) {
      case "hex":
        final hex = str.replaceAll(" ", "");
        for (var i = 0; i < hex.length / 2; i++) {
          if (i + offset < length) {
            this[i + offset] = int.parse(hex.substring(i * 2, i * 2 + 2), radix: 16);
          }
        }
        break;
      case "ascii":
        final hex = str.codeUnits.map((e) => e.toRadixString(16)).join("");
        return writeStringBE(hex, offset: offset, encoding: "hex");
    }
    return true;
  }

  bool writeStringLE(String str, {int offset = 0, String encoding = "hex"}) {
    switch (encoding.toLowerCase()) {
      case "hex":
        final hex = str.reversalEvery2Charts();
        return writeStringBE(hex, offset: offset, encoding: encoding);
      case "ascii":
        final hex = str.codeUnits.map((e) => e.toRadixString(16)).join("");
        return writeStringLE(hex, offset: offset, encoding: "hex");
    }
    return true;
  }

  bool writeStringBE2(String str, {int offset = 0, int length = 1, String encoding = "hex"}) {
    if (throwOffsetError(this, offset, length)) return false;
    switch (encoding.toLowerCase()) {
      case "hex":
        final hex = str.replaceAll(" ", "").padLeft(length * 2, '0').substring(0, length * 2);
        return writeStringBE(hex, offset: offset);
      case "ascii":
        final hex = str.codeUnits.map((e) => e.toRadixString(16)).join("");
        return writeStringBE2(hex, offset: offset, length: length, encoding: "hex");
    }
    return true;
  }

  bool writeStringLE2(String str, {int offset = 0, int length = 1, String encoding = "hex"}) {
    switch (encoding.toLowerCase()) {
      case "hex":
        final hex = str.reversalEvery2Charts().padRight(length * 2, '0').substring(0, length * 2);
        return writeStringBE2(hex, offset: offset, length: length, encoding: encoding);
      case "ascii":
        final hex = str.codeUnits.map((e) => e.toRadixString(16)).join("");
        return writeStringLE2(hex, offset: offset, length: length, encoding: "hex");
    }
    return true;
  }
}

bool throwOffsetError(List<int> byteArray, int offset, [int length = 1, int byteLength = 0]) {
  return offset > byteArray.length - length - byteLength;
}
